import GameInfo from './GameInfo';

class Snake {

    // 蛇头
    snake : HTMLElement;
    // 包含蛇头的蛇身
    bodies : HTMLCollection;
    // 蛇头运动方向
    direct : Direct = Direct.LEFT;

    gameInfo : GameInfo;

    // stepX，stepY 是逻辑坐标（非页面像素坐标），由于位移动画未结束，则拿不到蛇头的最终坐标，
    // 使用逻辑坐标转换成最终坐标，可以避免加速运动过程中（在位移动画未结束时转向导致）坐标位置计算不精确的情况出现
    // 还可以避免一直加速并连续转向，可以穿过贪吃蛇自己的身体（碰撞检测失效）的情况出现
    private stepX : number = 0;
    private stepY : number = 0;

    constructor(gameInfo:GameInfo){
        const panel = document.getElementById('snake')!;
        this.bodies = panel.getElementsByTagName('div');
        this.snake = <HTMLElement>this.bodies.item(0);
        this.snake.dataset.direct = this.direct.toString();
        this.gameInfo = gameInfo;
        this.reSet();
    }

    reSet(){
        while(this.bodies.length>1){
            this.bodies.item(this.bodies.length - 1)?.remove();
        }
        this.direct = randomDirect();
        // 蛇头是默认向右的箭头，所以需要根据初始化方向设置旋转角度
        if(this.direct==Direct.DOWN){
            this.snake.dataset.deg = '90';
            this.snake.style.transform = 'rotate(90deg)';
        } else if(this.direct==Direct.LEFT){
            this.snake.dataset.deg = '180';
            this.snake.style.transform = 'rotate(180deg)';
        } else if(this.direct==Direct.UP){
            this.snake.dataset.deg = '270';
            this.snake.style.transform = 'rotate(270deg)';
        }
        this.stepX = 15;
        this.stepY = 15;
        this.snake.style.left = this.stepX * 10 + 'px';
        this.snake.style.top = this.stepY * 10 + 'px';
        this.snake.dataset.stepX = this.stepX + '';
        this.snake.dataset.stepY = this.stepY + '';
    }

    /**
     * 获取蛇头的页面像素坐标 X
     */
    get X(){ // 使用逻辑坐标，可以避免在加速运动过程中吃不到食物的现象出现
        return this.stepX * 10;
    }

    
    /**
     * 获取蛇头的页面像素坐标 Y
     */
    get Y(){ // 使用逻辑坐标，可以避免在加速运动过程中吃不到食物的现象出现
        return this.stepY * 10;
    }

    /**
     * 是否已转换方向：防止在下一次位移之前多次转向（否则还是会出现掉头的情况），加速功能不受此限制；
     * 强制每位移一次，才能转变一次方向
     */
    private hasTurnDirection = false;

    /**
     * 可以让贪吃蛇从水平方向转向上运动，如果贪吃蛇已经在向上运动的话，则向上加速运动一格位置
     * @param gameOverHandle 处理游戏结束事件的函数（形参是游戏结束原因，类型是字符串）
     * @param eatFood 加速运动后，重新判定是否可以迟到食物，并处理相关事件
     */
    turnUp(gameOverHandle:Function, eatFood:Function){
        if(this.direct!==Direct.DOWN && this.direct!==Direct.UP && !this.hasTurnDirection) {   // 不能掉头
            this.hasTurnDirection = true;
            this.turnAround(this.direct, Direct.UP);
            this.direct = Direct.UP;
        } else if(this.direct===Direct.UP){
            this.move(gameOverHandle);
            eatFood();
            this.gameInfo.appendLog('向上加速');
        }
    }

    /**
     * 可以让贪吃蛇从水平方向转向下运动，如果贪吃蛇已经在向下运动的话，则向下加速运动一格位置
     * @param gameOverHandle 处理游戏结束事件的函数（形参是游戏结束原因，类型是字符串）
     * @param eatFood 加速运动后，重新判定是否可以迟到食物，并处理相关事件
     */
    turnDown(gameOverHandle:Function, eatFood:Function){
        if(this.direct!==Direct.DOWN && this.direct!==Direct.UP && !this.hasTurnDirection) {  // 不能掉头
            this.hasTurnDirection = true;
            this.turnAround(this.direct, Direct.DOWN);
            this.direct = Direct.DOWN;
        } else if(this.direct===Direct.DOWN){
            this.move(gameOverHandle);
            eatFood();
            this.gameInfo.appendLog('向下加速');
        }
    }

    /**
     * 可以让贪吃蛇从垂直方向转向左运动，如果贪吃蛇已经在向左运动的话，则向左加速运动一格位置
     * @param gameOverHandle 处理游戏结束事件的函数（形参是游戏结束原因，类型是字符串）
     * @param eatFood 加速运动后，重新判定是否可以迟到食物，并处理相关事件
     */
    turnLeft(gameOverHandle:Function, eatFood:Function){
        if(this.direct!==Direct.LEFT && this.direct!==Direct.RIGHT && !this.hasTurnDirection) {  // 不能掉头
            this.hasTurnDirection = true;
            this.turnAround(this.direct, Direct.LEFT);
            this.direct = Direct.LEFT;
        } else if(this.direct===Direct.LEFT){
            this.move(gameOverHandle);
            eatFood();
            this.gameInfo.appendLog('向左加速');
        }
    }

    /**
     * 可以让贪吃蛇从垂直方向转向右运动，如果贪吃蛇已经在向右运动的话，则向右加速运动一格位置
     * @param gameOverHandle 处理游戏结束事件的函数（形参是游戏结束原因，类型是字符串）
     * @param eatFood 加速运动后，重新判定是否可以迟到食物，并处理相关事件
     */
    turnRight(gameOverHandle:Function, eatFood:Function){
        if(this.direct!==Direct.LEFT && this.direct!==Direct.RIGHT && !this.hasTurnDirection) {  // 不能掉头
            this.hasTurnDirection = true;
            this.turnAround(this.direct, Direct.RIGHT);
            this.direct = Direct.RIGHT;
        } else if(this.direct===Direct.RIGHT){
            this.move(gameOverHandle);
            eatFood();
            this.gameInfo.appendLog('向右加速');
        }
    }

    /**
     * 转向动画，无法转动到相反的方向
     * @param from 原来的方向
     * @param to 转向后的方向
     */
    turnAround(from:string, to:string){
        let offset = -90;
        if(from===Direct.DOWN && to===Direct.LEFT){
            this.gameInfo.appendLog('从下向左转');
        } else if(from===Direct.DOWN && to===Direct.RIGHT){
            this.gameInfo.appendLog('从下向右转');
        } else if(from===Direct.UP && to===Direct.LEFT){
            this.gameInfo.appendLog('从上向左转');
        } else if(from===Direct.UP && to===Direct.RIGHT){
            this.gameInfo.appendLog('从上向右转');
        } else if(from===Direct.LEFT && to===Direct.DOWN){
            this.gameInfo.appendLog('从左向下转');
        } else if(from===Direct.LEFT && to===Direct.UP){
            this.gameInfo.appendLog('从左向上转');
        } else if(from===Direct.RIGHT && to===Direct.DOWN){
            this.gameInfo.appendLog('从右向下转');
        } else if(from===Direct.RIGHT && to===Direct.UP){
            this.gameInfo.appendLog('从右向上转');
        }
        if( (from===Direct.DOWN && to===Direct.LEFT) ||
            (from===Direct.LEFT && to===Direct.UP) ||
            (from===Direct.UP && to===Direct.RIGHT) ||
            (from===Direct.RIGHT && to===Direct.DOWN)
        ){
            offset = 90;
        }
        this.snake.dataset.deg = (parseInt(this.snake.dataset.deg!) + offset) + '';
        this.snake.style.transform = `rotate(${this.snake.dataset.deg}deg)`;
    }

    /**
     * 贪吃蛇身体自增长一节
     */
    addBody(){
        const div = document.createElement('div');
        const lastBody = <HTMLElement>this.bodies.item(this.bodies.length-1);
        const lastDirect = lastBody.dataset.direct;
        // 因为加入了动画，所以直接添加到最后一个元素上（是最后一个元素动画开始前的位置，而不是动画结束后的位置）
        div.style.left = parseInt(lastBody.dataset.stepX!)*10 + 'px';
        div.style.top = parseInt(lastBody.dataset.stepY!)*10 + 'px';
        div.dataset.direct = lastDirect;
        div.dataset.stepX = lastBody.dataset.stepX;
        div.dataset.stepY = lastBody.dataset.stepY;
        div.style.transform = lastBody.style.transform;
        this.snake.parentElement!.appendChild(div);
    }

    /**
     * 让贪吃蛇按照既定方向移动一格位置
     * @param gameOverHandle 处理游戏结束事件的函数（形参是游戏结束原因，类型是字符串）
     */
    move(gameOverHandle:Function){
        if(this.checkPosition(gameOverHandle)){
            for (let index = this.bodies.length-1; index >0; index--) {
                const element = <HTMLElement>this.bodies.item(index);
                const previous = <HTMLElement>this.bodies.item(index - 1);
                element.dataset.direct = previous.dataset.direct;
                element.dataset.stepX = previous.dataset.stepX;
                element.dataset.stepY = previous.dataset.stepY;
                element.style.left = parseInt(previous.dataset.stepX!)*10 + 'px';
                element.style.top = parseInt(previous.dataset.stepY!)*10 + 'px';
                element.style.transform = previous.style.transform;
            }
            this.snake.dataset.direct = this.direct;
            if(this.direct===Direct.UP){
                this.stepY--;
            } else if(this.direct===Direct.DOWN){
                this.stepY++;
            } else if(this.direct===Direct.LEFT){
                this.stepX--;
            } else if(this.direct===Direct.RIGHT){
                this.stepX++;
            }
            this.snake.style.left = this.stepX * 10 + 'px';
            this.snake.style.top = this.stepY * 10 + 'px';
            this.snake.dataset.stepX = this.stepX + '';
            this.snake.dataset.stepY = this.stepY + '';
            this.hasTurnDirection = false;
        }
    }

    /**
     * 检查某一个坐标是否在蛇身之上
     * @param x X 坐标
     * @param y Y 坐标
     * @returns true 表示该坐标在蛇身之上
     */
    checkBody(x:number, y:number){
        for (let index = 0; index < this.bodies.length; index++) {
            const element = this.bodies.item(index) as HTMLElement;
            if(parseInt(element.dataset.stepX!)*10===x && parseInt(element.dataset.stepY!)*10===y){
                return true;
            }
        }
        return false;
    }

    /**
     * 判定是否移动到了非法位置
     * @param gameOverHandle 处理游戏结束事件的函数（形参是游戏结束原因，类型是字符串）
     * @returns 
     */
    private checkPosition(gameOverHandle:Function):boolean{
        let reason = '';
        
        if(this.direct===Direct.UP && this.Y - 10<0){
            reason = '撞到上边墙了';
        } else if(this.direct===Direct.DOWN && this.Y + 10>290){
            reason = '撞到下边墙了';
        } else if(this.direct===Direct.LEFT && this.X - 10<0){
            reason = '撞到左边墙了';
        } else if(this.direct===Direct.RIGHT && this.X + 10>290){
            reason = '撞到右边墙了';
        }
        let stpx:any = this.stepX, stpy:any = this.stepY;
        if(this.direct===Direct.UP){
            stpy--;
        } else if(this.direct===Direct.DOWN){
            stpy++;
        } else if(this.direct===Direct.LEFT){
            stpx--;
        } else if(this.direct===Direct.RIGHT){
            stpx++;
        }
        // 类型转换成 string，下面用===比较才不会一直false
        stpx = stpx + '';
        stpy = stpy + '';
        if(!reason && this.bodies.length>3){
            for (let index = 3; index < this.bodies.length; index++) {
                const element = this.bodies.item(index) as HTMLElement;
                if(element.dataset.stepX===stpx && element.dataset.stepY===stpy){
                    reason = `撞到自己第 ${index + 1} 节身体了`;
                    element.style.backgroundColor = 'red';
                    break;
                }
            }
        }

        if(reason){
            gameOverHandle(reason);
            return false;
        }
        return true;
    }
}

enum Direct {
    UP = 'UP',
    DOWN = 'DOWN',
    LEFT = 'LEFT',
    RIGHT = 'RIGHT'
}

function randomDirect(){
    const arr =  [Direct.UP, Direct.DOWN, Direct.LEFT, Direct.RIGHT];
    return arr[Math.round(Math.random()*2)];
}

export default Snake;